package pl.poznan.put.cs.idss.students.kingcart.core.model;

import static com.googlecode.javacv.cpp.opencv_core.*;
import static com.googlecode.javacv.cpp.opencv_imgproc.*;
import static com.googlecode.javacv.cpp.opencv_highgui.*;

import java.awt.Point;
import java.awt.Rectangle;

import pl.poznan.put.cs.idss.students.kingcart.properties.Properties;

import com.googlecode.javacv.cpp.opencv_core.*;
import com.googlecode.javacv.cpp.opencv_core.CvRect;
import com.googlecode.javacv.cpp.opencv_core.IplImage;
import com.googlecode.javacv.cpp.opencv_imgproc.CvHistogram;

public class FrameFeatures implements Features {
	/**
	 * o jaki % rozmiaru ramki mogl przemisecic sie obiekt
	 */
	private final double RATIO = 4;
	/**
	 * maksymalna dopuszczalna odleglosc miedzy dwoma kolejnymi pomiarami -
	 * jesli wieksza niz ratio * wyznaczona wartosc, to blad
	 */
	private double maxDist;

	/**
	 * wybrany do śledzenia fragment
	 */
	IplImage originalPattern;
	/**
	 * obecnie śledzony fragment
	 */
	IplImage currentPattern;

	Rectangle importantArea;

	Point currentLocation;

	CvRect lastBoundBox;
	/**
	 * maksymalna dopuszczalna roznica w rozmiarach konturow ( > 1	!)	
	 */
	private double maxDiff = 3;

	public CvRect getLastBoundBox() {
		return lastBoundBox;
	}

	public void setLastBoundBox(CvRect lastBoundBox) {
		this.lastBoundBox = lastBoundBox;
	}

	public Point getCurrentLocation() {
		return currentLocation;
	}

	public void setCurrentLocation(Point currentLocation) {
		this.currentLocation = currentLocation;
	}

	public void setFeatures(IplImage frame, Rectangle importantArea) {
		int width = Properties.number("canvas_width");
		int height = Properties.number("canvas_height");
		// nalezalo przeskalowac ze wzgledu na to, ze zaznaczony obszar dotyczy
		// rozmiaru canvasa, a nie ramki
		IplImage resizedFrame = cvCreateImage(cvSize(width, height),
				frame.depth(), frame.nChannels());
		cvResize(frame, resizedFrame, CV_INTER_CUBIC);

		CvRect cvRect = cvRect((int) importantArea.getMinX(),
				(int) importantArea.getMinY(), importantArea.width,
				importantArea.height);

		double ratio = frame.width() / (double) width;
		setImportantArea(cvRect, ratio);

		maxDist = Math.max(frame.width(), frame.height()) / RATIO;

		currentLocation = new Point((int) this.importantArea.getCenterX(),
				(int) this.importantArea.getCenterY());

		originalPattern = cropImage(resizedFrame, cvRect);
		currentPattern = cropImage(resizedFrame, cvRect);
	}

	private void setImportantArea(CvRect cvRect, double ratio) {
		int y = (int) (cvRect.y() * ratio);
		int x = (int) (cvRect.x() * ratio);
		int width = (int) (cvRect.width() * ratio);
		int height = (int) (cvRect.height() * ratio);
		importantArea = new Rectangle(x, y, width, height);
		lastBoundBox = new CvRect(x, y, width, height);
	}

	/**
	 * mierzy odleglosci od srodkow konturow
	 */
	public double rectDist(CvRect rect) {
		int x = rect.x() + rect.width() / 2;
		int y = rect.y() + rect.height() / 2;
		Point rectCenter = new Point(x, y);
		return rectCenter.distance(currentLocation);
	}

	/**
	 * Wycina zadany fragment z obrazka
	 * 
	 * @param frame
	 * @param rect
	 * @return
	 */
	private IplImage cropImage(IplImage frame, CvRect rect) {

		cvSetImageROI(frame, rect);
		IplImage cropped = cvCreateImage(cvGetSize(frame), frame.depth(),
				frame.nChannels());
		cvCopy(frame, cropped, null);
		cvResetImageROI(frame);

		// cvShowImage("IMG", cropped);
		// cvWaitKey(10);

		return cropped;
	}
	
	public double similarity(IplImage frame, CvRect rect){
		// im wieksza korelacja, tym lepiej
		double mark_ratio = 1 * compareHistogram(frame, rect);
	//	double mark_ratio = 1;
		double mark = similarityOfBoundBox(frame, rect);
		return mark * mark_ratio;
	}

	public double similarityOfBoundBox(IplImage frame, CvRect rect) {
		//roznica miedzy rozmiarami (zawsze >= 1)
		double sizeDiff =  1/sizeDiff(rect);
		// im mniejsza odleglosc, tym lepiej - przy czym waga odleglosci 2*
		// wieksza od histogramu
		double mark =2 / (rectDist(rect) / Point.distance(0, 0, frame.width(),
				frame.height()));
		return sizeDiff * mark;

	}

	public double similarityOfHistograms(IplImage frame, CvRect rect) {
		return compareHistogram(frame, rect);
	}

	private double compareHistogram(IplImage frame, CvRect rect) {
		IplImage cropImage = cropImage(frame, rect);
		IplImage[] currChannels = new IplImage[3];
		IplImage[] patternChannels = new IplImage[3];

		CvHistogram[] currHistograms = new CvHistogram[3];
		CvHistogram[] patternHistograms = new CvHistogram[3];

		float[][] minMax = { { 0f, 255f } };

		for (int i = 0; i < 3; i++) {
			currChannels[i] = IplImage.create(cropImage.width(),
					cropImage.height(), IPL_DEPTH_8U, 1);
			patternChannels[i] = IplImage.create(currentPattern.width(),
					currentPattern.height(), IPL_DEPTH_8U, 1);
			currHistograms[i] = cvCreateHist(1, new int[] { 256 },
					CV_HIST_ARRAY, minMax, 1);
			patternHistograms[i] = cvCreateHist(1, new int[] { 256 },
					CV_HIST_ARRAY, minMax, 1);
		}

		cvSplit(cropImage, currChannels[0], currChannels[1], currChannels[2],
				null);
		cvSplit(currentPattern, patternChannels[0], patternChannels[1],
				patternChannels[2], null);

		double cvCompareHist = 0;

		for (int i = 0; i < 3; i++) {
			cvCalcHist(new IplImage[] { currChannels[i] }, currHistograms[i],
					0, null);
			cvCalcHist(new IplImage[] { patternChannels[i] },
					patternHistograms[i], 0, null);
			cvCompareHist += cvCompareHist(currHistograms[i],
					patternHistograms[i], CV_COMP_CORREL);
		}

		return cvCompareHist;
	}

	public boolean isVeto(CvRect boundBox) {
		if (
			  rectDist(boundBox) > maxDist ||
			 sizeDiff(boundBox) > maxDiff)
			return true;
		return false;
	}

	private double sizeDiff(CvRect boundBox) {
		double lastArea = lastBoundBox.width() * lastBoundBox.height();
		double currArea = boundBox.width() * boundBox.height();
		if (lastArea > currArea)
			return lastArea / currArea;
		return currArea / lastArea;
	}
}
